Išmokite kurti didelio pralaidumo lygiagretų procesorių JavaScript kalba su asinchroniniais iteratoriais. Pagerinkite srautų valdymą ir pagreitinkite duomenims imlias programas.
Didelio našumo JavaScript atrakinimas: Išsamus nardymas į iteratorių pagalbinius lygiagrečius procesorius lygiagrečiam srautų valdymui
Šiuolaikinės programinės įrangos kūrimo pasaulyje našumas nėra funkcija; tai yra pagrindinis reikalavimas. Nuo didelių duomenų rinkinių apdorojimo užpakalinėje tarnyboje iki sudėtingų API sąveikų tvarkymo žiniatinklio programoje, gebėjimas efektyviai valdyti asinchronines operacijas yra gyvybiškai svarbus. JavaScript, turinti vieno sriegio, įvykiais pagrįstą modelį, ilgą laiką puikiai pasižymėjo I/O užduotyse. Tačiau, augant duomenų kiekiui, tradiciniai nuoseklūs apdorojimo metodai tampa reikšmingomis kliūtimis.
Įsivaizduokite, kad reikia gauti išsamią informaciją apie 10 000 produktų, apdoroti gigabaito dydžio žurnalo failą arba sugeneruoti miniatiūras šimtams vartotojų įkeltų vaizdų. Šių užduočių tvarkymas po vieną yra patikimas, bet skausmingai lėtas. Raktas į dramatišką našumo padidėjimą slypi lygiagretume – kelių elementų apdorojime vienu metu. Čia asinchroninių iteratorių galia, derinama su pasirinktine lygiagretaus apdorojimo strategija, keičia mūsų duomenų srautų tvarkymą.
Šis išsamus vadovas skirtas pažengusiems JavaScript kūrėjams, norintiems išeiti už pagrindinių `async/await` ciklų ribų. Mes išnagrinėsime JavaScript iteratorių pagrindus, pasinersime į nuoseklių kliūčių problemą ir, svarbiausia, nuo nulio sukursime galingą, daugkartinio naudojimo Iteratorių Pagalbinį Lygiagretų Procesorių. Šis įrankis leis jums valdyti lygiagrečias užduotis bet kuriame duomenų sraute su smulkiu valdymu, todėl jūsų programos bus greitesnės, efektyvesnės ir labiau mastelio.
Pagrindų supratimas: Iteratoriai ir asinchroninis JavaScript
Prieš kurdami savo lygiagretų procesorių, turime gerai suprasti pagrindines JavaScript koncepcijas, kurios tai leidžia: iteratoriaus protokolus ir jų asinchroninius atitikmenis.
Iteratorių ir iteruojamųjų objektų galia
Iš esmės, iteratoriaus protokolas suteikia standartinį būdą reikšmių sekai gauti. Objektas laikomas iteruojamuoju, jei jis įgyvendina metodą su raktu `Symbol.iterator`. Šis metodas grąžina iteratoriaus objektą, kuris turi `next()` metodą. Kiekvienas `next()` iškvietimas grąžina objektą su dviem savybėmis: `value` (kita reikšmė sekoje) ir `done` (būlio reikšmė, nurodanti, ar seka baigta).
Šis protokolas yra `for...of` ciklo magija ir yra natūraliai įdiegiamas daugelyje įmontuotų tipų:
- Masyvai: `['a', 'b', 'c']`
- Eilutės: `"hello"`
- Žemėlapiai: `new Map([['key1', 'value1'], ['key2', 'value2']])`
- Aibės: `new Set([1, 2, 3])`
Iteruojamųjų objektų grožis yra tas, kad jie atvaizduoja duomenų srautus tingiuoju būdu. Reikšmes traukiate po vieną, o tai yra neįtikėtinai efektyvu atminties atžvilgiu didelėms ar net begalinėms sekomis, nes nereikia viso duomenų rinkinio laikyti atmintyje vienu metu.
Asinchroninių iteratorių atsiradimas
Standartinis iteratoriaus protokolas yra sinchroninis. O kas, jei mūsų sekoje esančios reikšmės nėra iš karto pasiekiamos? O kas, jei jos gaunamos iš tinklo užklausos, duomenų bazės kursoriaus ar failo srauto? Čia atsiranda asinchroniniai iteratoriai.
Asinchroninis iteratoriaus protokolas yra artimas savo sinchroninio atitikmens pusbrolis. Objektas yra asinchroniškai iteruojamas, jei jis turi metodą, kurio raktas yra `Symbol.asyncIterator`. Šis metodas grąžina asinchroninį iteratorių, kurio `next()` metodas grąžina `Promise`, kuris išsprendžiamas į pažįstamą `{ value, done }` objektą.
Tai leidžia mums dirbti su duomenų srautais, kurie atsiranda laikui bėgant, naudojant elegantišką `for await...of` ciklą:
Pavyzdys: Asinchroninis generatorius, kuris pateikia skaičius su vėlavimu.
async function* createDelayedNumberStream() {
for (let i = 1; i <= 5; i++) {
// Simulate a network delay or other async operation
await new Promise(resolve => setTimeout(resolve, 500));
yield i;
}
}
async function consumeStream() {
const numberStream = createDelayedNumberStream();
console.log('Starting consumption...');
// The loop will pause at each 'await' until the next value is ready
for await (const number of numberStream) {
console.log(`Received: ${number}`);
}
console.log('Consumption finished.');
}
// Output will show numbers appearing every 500ms
Šis šablonas yra esminis šiuolaikiniam duomenų apdorojimui Node.js ir naršyklėse, leidžiantis mums elegantiškai tvarkyti didelius duomenų šaltinius.
Pristatome Iteratorių pagalbinių priemonių pasiūlymą
Nors `for...of` ciklai yra galingi, jie gali būti imperatyvūs ir daugiažodiai. Masyvams turime platų deklaratyvių metodų rinkinį, tokių kaip `.map()`, `.filter()` ir `.reduce()`. TC39 Iteratorių pagalbinių priemonių pasiūlymas siekia suteikti tą pačią išraiškingą galią tiesiogiai iteratoriams.
Šis pasiūlymas prideda metodus prie `Iterator.prototype` ir `AsyncIterator.prototype`, leidžiančius mums sujungti operacijas bet kuriame iteruojamame šaltinyje, prieš tai nekonvertuojant jo į masyvą. Tai yra esminis pokytis atminties efektyvumui ir kodo aiškumui.
Apsvarstykite šį „prieš ir po“ scenarijų duomenų srauto filtravimui ir atvaizdavimui:
Prieš (su standartiniu ciklu):
async function processData(source) {
const results = [];
for await (const item of source) {
if (item.value > 10) { // filter
const processedItem = await transform(item); // map
results.push(processedItem);
}
}
return results;
}
Po (su siūlomomis asinchroninių iteratorių pagalbinėmis priemonėmis):
async function processDataWithHelpers(source) {
const results = await source
.filter(item => item.value > 10)
.map(async item => await transform(item))
.toArray(); // .toArray() is another proposed helper
return results;
}
Nors šis pasiūlymas dar nėra standartinė kalbos dalis visose aplinkose, jo principai sudaro koncepcinį pagrindą mūsų lygiagrečiam procesoriui. Mes norime sukurti į `map` panašią operaciją, kuri apdoroja ne tik vieną elementą vienu metu, bet ir paleidžia kelias `transform` operacijas lygiagrečiai.
Kliūtis: nuoseklus apdorojimas asinchroniniame pasaulyje
Ciklas `for await...of` yra fantastiškas įrankis, tačiau jis turi esminę savybę: jis yra nuoseklus. Ciklo kūnas nepradedamas kitam elementui, kol visiškai nebaigtos dabartinio elemento `await` operacijos. Tai sukuria našumo ribą, kai dirbama su nepriklausomomis užduotimis.
Iliustruokime tai įprastu, realaus pasaulio scenarijumi: duomenų gavimas iš API, skirtas identifikatorių sąrašui.
Įsivaizduokite, kad turime asinchroninį iteratorių, kuris pateikia 100 vartotojų ID. Kiekvienam ID turime atlikti API iškvietimą, kad gautume vartotojo profilį. Tarkime, kad kiekvienas API iškvietimas trunka vidutiniškai 200 milisekundžių.
async function fetchUserProfile(userId) {
// Simulate an API call
await new Promise(resolve => setTimeout(resolve, 200));
return { id: userId, name: `User ${userId}`, fetchedAt: new Date() };
}
async function fetchAllUsersSequentially(userIds) {
console.time('SequentialFetch');
const profiles = [];
for await (const id of userIds) {
const profile = await fetchUserProfile(id);
profiles.push(profile);
console.log(`Fetched user ${id}`);
}
console.timeEnd('SequentialFetch');
return profiles;
}
// Assuming 'userIds' is an async iterable of 100 IDs
// await fetchAllUsersSequentially(userIds);
Koks yra bendras vykdymo laikas? Kadangi kiekviena `await fetchUserProfile(id)` operacija turi būti baigta prieš pradedant kitą, bendras laikas bus apytiksliai:
100 vartotojų * 200 ms/vartotojas = 20 000 ms (20 sekundžių)
Tai yra klasikinė I/O susijusi kliūtis. Kol mūsų JavaScript procesas laukia tinklo, jo įvykių ciklas dažniausiai yra tuščias. Mes neišnaudojame visos sistemos ar išorinės API galimybės. Apdorojimo laiko juosta atrodo taip:
Užduotis 1: [---LAUKIMAS---] Atlikta
Užduotis 2: [---LAUKIMAS---] Atlikta
Užduotis 3: [---LAUKIMAS---] Atlikta
...ir t.t.
Mūsų tikslas yra pakeisti šią laiko juostą į kažką panašaus, naudojant 10 lygiagretumo lygį:
Užduotis 1-10: [---LAUKIMAS---][---LAUKIMAS---]... Atlikta
Užduotis 11-20: [---LAUKIMAS---][---LAUKIMAS---]... Atlikta
...
Su 10 lygiagrečių operacijų teoriškai galime sumažinti bendrą laiką nuo 20 sekundžių iki vos 2 sekundžių. Tai yra našumo šuolis, kurį siekiame pasiekti, sukūrę savo lygiagretų procesorių.
JavaScript iteratorių pagalbinio lygiagretaus procesoriaus kūrimas
Dabar pasiekiame šio straipsnio esmę. Sukursime daugkartinio naudojimo asinchroninio generatoriaus funkciją, kurią pavadinsime `parallelMap`, kuri priims asinchroninį iteruojamą šaltinį, atvaizdavimo funkciją ir lygiagretumo lygį. Ji sukurs naują asinchroninį iteruojamą objektą, kuris pateiks apdorotus rezultatus, kai tik jie taps prieinami.
Pagrindiniai projektavimo principai
- Lygiagretumo ribojimas: Procesorius niekada neturi turėti daugiau nei nurodytas skaičius `mapper` funkcijos pažadų vienu metu. Tai yra kritiškai svarbu resursų valdymui ir išorinių API iškvietimų limitų laikymuisi.
- Tingus vartojimas: Jis turi traukti iš šaltinio iteratoriaus tik tada, kai jo apdorojimo baseine yra laisva vieta. Tai užtikrina, kad visas šaltinis nebūtų buferizuojamas atmintyje, išsaugant srautų privalumus.
- Atgalinio slėgio valdymas: Procesorius turėtų natūraliai sustoti, jei jo išvesties vartotojas yra lėtas. Asinchroniniai generatoriai tai pasiekia automatiškai per `yield` raktinį žodį. Kai vykdymas sustabdomas ties `yield`, nauji elementai iš šaltinio netraukiami.
- Netvarkinga išvestis maksimaliam pralaidumui: Norint pasiekti didžiausią įmanomą greitį, mūsų procesorius pateiks rezultatus, kai tik jie bus paruošti, nebūtinai originalia įvesties tvarka. Apie tai, kaip išsaugoti tvarką, aptarsime vėliau kaip pažangią temą.
The `parallelMap` įgyvendinimas
Sukurkime savo funkciją žingsnis po žingsnio. Geriausias įrankis pasirinktiniam asinchroniniam iteratoriui sukurti yra `async function*` (asinchroninis generatorius).
/**
* Creates a new async iterable that processes items from a source iterable in parallel.
* @param {AsyncIterable|Iterable} source The source iterable to process.
* @param {Function} mapperFn An async function that takes an item and returns a promise of the processed result.
* @param {object} options
* @param {number} options.concurrency The maximum number of tasks to run in parallel.
* @returns {AsyncGenerator} An async generator that yields the processed results.
*/
async function* parallelMap(source, mapperFn, { concurrency = 5 }) {
// 1. Get the async iterator from the source.
// This works for both sync and async iterables.
const asyncIterator = source[Symbol.asyncIterator] ?
source[Symbol.asyncIterator]() :
source[Symbol.iterator]();
// 2. A set to keep track of the promises for the currently processing tasks.
// Using a Set makes adding and deleting promises efficient.
const processing = new Set();
// 3. A flag to track if the source iterator is exhausted.
let sourceIsDone = false;
// 4. The main loop: continues as long as there are tasks processing
// or the source has more items.
while (!sourceIsDone || processing.size > 0) {
// 5. Fill the processing pool up to the concurrency limit.
while (processing.size < concurrency && !sourceIsDone) {
const nextItemPromise = asyncIterator.next();
const processingPromise = nextItemPromise.then(item => {
if (item.done) {
sourceIsDone = true;
return; // Signal that this branch is done, no result to process.
}
// Execute the mapper function and ensure its result is a promise.
// This returns the final processed value.
return Promise.resolve(mapperFn(item.value));
});
// This is a crucial step for managing the pool.
// We create a wrapper promise that, when it resolves, gives us both
// the final result and a reference to itself, so we can remove it from the pool.
const trackedPromise = processingPromise.then(result => ({
result,
origin: trackedPromise
}));
processing.add(trackedPromise);
}
// 6. If the pool is empty, we must be done. Break the loop.
if (processing.size === 0) break;
// 7. Wait for ANY of the processing tasks to complete.
// Promise.race() is the key to achieving this.
const { result, origin } = await Promise.race(processing);
// 8. Remove the completed promise from the processing pool.
processing.delete(origin);
// 9. Yield the result, unless it's the 'undefined' from a 'done' signal.
// This pauses the generator until the consumer requests the next item.
if (result !== undefined) {
yield result;
}
}
}
Logikos išskaidymas
- Inicializavimas: Iš šaltinio gauname asinchroninį iteratorių ir inicijuojame `Set` pavadinimu `processing`, kuris veiks kaip mūsų lygiagretumo baseinas.
- Baseino užpildymas: Vidinis `while` ciklas yra variklis. Jis patikrina, ar `processing` rinkinyje yra vietos ir ar `source` dar turi elementų. Jei taip, jis traukia kitą elementą.
- Užduoties vykdymas: Kiekvienam elementui iškviečiame `mapperFn`. Visa operacija – kito elemento gavimas ir jo atvaizdavimas – yra apvyniota pažadu (`processingPromise`).
- Pažadų sekimas: Sunkiausia dalis yra žinoti, kurį pažadą pašalinti iš rinkinio po `Promise.race()`. `Promise.race()` grąžina išspręstą reikšmę, o ne patį pažado objektą. Norėdami tai išspręsti, sukuriame `trackedPromise`, kuris išsprendžiamas į objektą, kuriame yra tiek galutinis `result`, tiek nuoroda į jį patį (`origin`). Šį sekimo pažadą pridedame prie savo `processing` rinkinio.
- Laukimas greičiausios užduoties: `await Promise.race(processing)` sustabdo vykdymą, kol baigsis pirmoji užduotis baseine. Tai yra mūsų lygiagretumo modelio širdis.
- Pateikimas ir papildymas: Kai užduotis baigiasi, gauname jos rezultatą. Iš `processing` rinkinio pašaliname atitinkamą `trackedPromise` o tai atlaisvina vietą. Tada `yield` pateikiame rezultatą. Kai vartotojo ciklas paprašys kito elemento, mūsų pagrindinis `while` ciklas tęsis, o vidinis `while` ciklas bandys užpildyti tuščią vietą nauja užduotimi iš šaltinio.
Taip sukuriama savarankiškai reguliuojama konvejerio sistema. Baseinas nuolat ištuštinamas `Promise.race` ir papildomas iš šaltinio iteratoriaus, palaikant stabilų lygiagrečių operacijų būseną.
Mūsų `parallelMap` naudojimas
Grįžkime prie mūsų vartotojų gavimo pavyzdžio ir pritaikykime mūsų naująją priemonę.
// Assume 'createIdStream' is an async generator yielding 100 user IDs.
const userIdStream = createIdStream();
async function fetchAllUsersInParallel() {
console.time('ParallelFetch');
const profilesStream = parallelMap(userIdStream, fetchUserProfile, { concurrency: 10 });
for await (const profile of profilesStream) {
console.log(`Processed profile for user ${profile.id}`);
}
console.timeEnd('ParallelFetch');
}
// await fetchAllUsersInParallel();
Esant 10 lygiagretumo, bendras vykdymo laikas dabar bus apytiksliai 2 sekundės vietoj 20. Mes pasiekėme 10 kartų didesnį našumą, tiesiog apvyniodami savo srautą `parallelMap`. Grožis yra tas, kad vartojantis kodas išlieka paprastas, skaitomas `for await...of` ciklas.
Praktiniai naudojimo atvejai ir globalūs pavyzdžiai
Didelio pralaidumo API sąveika
Scenarijus: Finansinių paslaugų programai reikia praturtinti operacijų duomenų srautą. Kiekvienai operacijai ji turi iškviesti dvi išorines API: vieną – sukčiavimo aptikimui, kitą – valiutos konvertavimui. Šios API turi 100 užklausų per sekundę ribą.
Sprendimas: Naudokite `parallelMap` su `concurrency` nustatymu `20` arba `30`, kad apdorotumėte operacijų srautą. `mapperFn` atliktų du API iškvietimus, naudodamas `Promise.all`. Lygiagretumo limitas užtikrina didelį pralaidumą, neviršijant API iškvietimų limitų, o tai yra kritiškai svarbu bet kuriai programai, sąveikaujančiai su trečiųjų šalių paslaugomis.
Didelio masto duomenų apdorojimas ir ETL (Extrakcija, Transformacija, Įkėlimas)
Scenarijus: Duomenų analizės platformai Node.js aplinkoje reikia apdoroti 5 GB CSV failą, saugomą debesies kaupiklyje (pvz., Amazon S3 ar Google Cloud Storage). Kiekviena eilutė turi būti patvirtinta, išvalyta ir įterpta į duomenų bazę.
Sprendimas: Sukurkite asinchroninį iteratorių, kuris skaito failą iš debesies saugyklos srauto eilutė po eilutės (pvz., naudojant `stream.Readable` Node.js). Nukreipkite šį iteratorių į `parallelMap`. `mapperFn` atliks validavimo logiką ir duomenų bazės `INSERT` operaciją. `concurrency` galima reguliuoti atsižvelgiant į duomenų bazės jungčių baseino dydį. Šis metodas leidžia išvengti 5 GB failo įkėlimo į atmintį ir lygiagrečiai apdoroja lėtą duomenų bazės įterpimo dalį.
Vaizdo ir vaizdo transkodavimo konvejeris
Scenarijus: Pasaulinė socialinės žiniasklaidos platforma leidžia vartotojams įkelti vaizdo įrašus. Kiekvienas vaizdo įrašas turi būti transkoduotas į kelias raiškas (pvz., 1080p, 720p, 480p). Tai yra procesoriaus intensyvi užduotis.
Sprendimas: Kai vartotojas įkelia vaizdo įrašų partiją, sukurkite vaizdo failų kelių iteratorių. `mapperFn` gali būti asinchroninė funkcija, kuri sukuria antrinį procesą, kad paleistų komandų eilutės įrankį, pvz., `ffmpeg`. `concurrency` turėtų būti nustatyta pagal mašinoje esančių procesoriaus branduolių skaičių (pvz., `os.cpus().length` Node.js), kad būtų maksimaliai išnaudojama aparatinė įranga, neperkraunant sistemos.
Pažangios koncepcijos ir svarstymai
Nors mūsų `parallelMap` yra galingas, realaus pasaulio programoms dažnai reikia daugiau niuansų.
Patikimas klaidų tvarkymas
Kas nutiks, jei vienas iš `mapperFn` iškvietimų bus atmestas? Dabartinėje mūsų implementacijoje `Promise.race` bus atmestas, o tai sukels visam `parallelMap` generatoriui klaidą ir nutraukimą. Tai yra „greitojo gedimo“ strategija.
Dažnai norite atsparesnio konvejerio, kuris gali atlaikyti individualius gedimus. Tai galite pasiekti apvynioję savo `mapperFn`.
const resilientMapper = async (item) => {
try {
return { status: 'fulfilled', value: await originalMapper(item) };
} catch (error) {
console.error(`Failed to process item ${item.id}:`, error);
return { status: 'rejected', reason: error, item: item };
}
};
const resultsStream = parallelMap(source, resilientMapper, { concurrency: 10 });
for await (const result of resultsStream) {
if (result.status === 'fulfilled') {
// process successful value
} else {
// handle or log the failure
}
}
Tvarkos išlaikymas
Mūsų `parallelMap` pateikia rezultatus netvarkingai, teikdamas pirmenybę greičiui. Kartais išvesties tvarka turi atitikti įvesties tvarką. Tam reikalingas kitoks, sudėtingesnis įgyvendinimas, dažnai vadinamas `parallelOrderedMap`.
Bendroji tvarkingos versijos strategija yra tokia:
- Apdorokite elementus lygiagrečiai, kaip ir anksčiau.
- Vietoj to, kad iškart pateiktumėte rezultatus, saugokite juos buferyje arba žemėlapyje, su raktais pagal jų pradinį indeksą.
- Palaikykite skaitiklį kitam tikėtinam indeksui, kuris turi būti pateiktas.
- Cikle patikrinkite, ar dabartiniam tikėtinam indeksui skirtas rezultatas yra buferyje. Jei taip, pateikite jį, padidinkite skaitiklį ir pakartokite. Jei ne, palaukite, kol bus baigtos kitos užduotys.
Atgalinio slėgio paaiškinimas
Verta pakartoti vieną iš elegantiškiausių šio asinchroninio generatoriaus pagrindu sukurto metodo ypatybių: automatinį atgalinio slėgio valdymą. Jei kodas, naudojantis mūsų `parallelMap`, yra lėtas – pavyzdžiui, įrašo kiekvieną rezultatą į lėtą diską ar perkrautą tinklo lizdą – `for await...of` ciklas neprašys kito elemento. Tai priverčia mūsų generatorių sustoti ties `yield result;` eilute. Kai jis sustabdomas, jis nekuria ciklo, neiškviečia `Promise.race` ir, svarbiausia, neužpildo apdorojimo baseino. Šis paklausos trūkumas plinta atgal iki pat originalaus šaltinio iteratoriaus, iš kurio neskaitoma. Visa konvejerio sistema automatiškai sulėtėja, kad atitiktų lėčiausio komponento greitį, užkertant kelią atminties perkrovai dėl per didelio buferizavimo.
Išvados ir ateities perspektyvos
Mes nuėjome kelią nuo pagrindinių JavaScript iteratorių koncepcijų iki sudėtingos, didelio našumo lygiagretaus apdorojimo priemonės sukūrimo. Perėję nuo nuoseklių `for await...of` ciklų prie valdomo lygiagretaus modelio, parodėme, kaip pasiekti didelių našumo pagerėjimų duomenims imlioms, I/O ir procesoriaus ribotoms užduotims.
Pagrindinės išvados yra šios:
- Nuoseklus apdorojimas yra lėtas: Tradiciniai asinchroniniai ciklai yra nepriklausomų užduočių kliūtis.
- Lygiagretumas yra esminis: Elementų apdorojimas lygiagrečiai žymiai sumažina bendrą vykdymo laiką.
- Asinchroniniai generatoriai yra tobulas įrankis: Jie suteikia švarią abstrakciją pasirinktiniams iteruojamiems objektams kurti su integruota parama esminėms funkcijoms, tokioms kaip atgalinis slėgis.
- Valdymas yra būtinas: Valdomas lygiagretumo baseinas apsaugo nuo resursų išeikvojimo ir laikosi išorinių sistemų apribojimų.
Kadangi JavaScript ekosistema toliau vystosi, Iteratorių pagalbinių priemonių pasiūlymas greičiausiai taps standartine kalbos dalimi, suteikiančia tvirtą, gimtąjį pagrindą srautų manipuliavimui. Tačiau paralelizavimo logika – pažadų baseino valdymas naudojant įrankį, pvz., `Promise.race` – išliks galingu, aukštesnio lygio šablonu, kurį kūrėjai gali įgyvendinti, kad išspręstų konkrečius našumo iššūkius.
Skatindamas, siūlau išbandyti šiandien sukurtą `parallelMap` funkciją ir eksperimentuoti su ja savo projektuose. Nustatykite savo kliūtis, nesvarbu, ar tai būtų API iškvietimai, duomenų bazės operacijos ar failų apdorojimas, ir pažiūrėkite, kaip šis lygiagretaus srautų valdymo modelis gali padaryti jūsų programas greitesnes, efektyvesnes ir paruoštas duomenimis grįsto pasaulio poreikiams.